package atom.core3.apache.http4;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.net.ConnectException;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.net.ssl.SSLContext;

import org.apache.http.Header;
import org.apache.http.HttpHost;
import org.apache.http.HttpStatus;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.cookie.Cookie;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.apache.log4j.Logger;

import atom.core0.exception.BizConnectException;
import atom.core0.exception.BizException;
import atom.core0.exception.BizSysException;
import atom.core0.exception.UnknowStateException;

/**
 * <p>
 * Title:
 * </p>
 * <p>
 * Description:HttpClient
 * </p>
 * <p>
 * Copyright: Copyright (c) 2010
 * </p>
 * <p>
 * </p>
 * 
 * @author 赵明才
 * @version 1.0 6.1 DefaultHttpClient is deprecated DefaultHttpClient --->
 *          CloseableHttpClient HttpResponse ---> CloseableHttpResponse
 */
public class HttpClientAgent {
	private static Logger log = Logger.getLogger(HttpClientAgent.class);
	
	//注意:static的参数 全部请求都是一样的
	private static HttpHost proxy=null;
	private static int errorRetryCount = 0;
	private static SSLConnectionSocketFactory ssff = null;

	private CloseableHttpClient httpClient;
	protected HttpUriRequest postgetMethod = null;
	private CloseableHttpResponse httpResponse;
	private BasicCookieStore cookieStore = new BasicCookieStore();// 共享cookies
	private HttpClientContext httpContext = new HttpClientContext();// 共享上下文
																	// 这个不重要

	private HashMap headMap = new HashMap();
	private HashMap parmMap = new HashMap();
	private int connecttimeout; // 秒
	private int readtimeout; // 秒
	protected String charset;


	//NoopHostnameVerifier 创建HostnameVerifier
    //用于解决javax.net.ssl.SSLException: hostname in certificate didn't match: <123.125.97.66> != <123.125.97.241>
	
	public CloseableHttpClient initHttpClient(int maxTotal, int maxPerRoute) throws Exception{
		
		SSLContext sslcontext = SSLContexts.custom().build();

		Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory> create()
				.register("http", PlainConnectionSocketFactory.INSTANCE)
				.register("https", new SSLConnectionSocketFactory(sslcontext, new NoopHostnameVerifier())).build();
		
		// 池化的连接管理
		//maxTotal ,创建socket的上线是200
		//maxPerRoute, 对每个指定连接的服务器（指定的ip）可以创建并发20 socket进行访问
		PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(socketFactoryRegistry);//
		cm.setMaxTotal(maxTotal);
		cm.setDefaultMaxPerRoute(maxPerRoute);
							
		// 设置超时时间        超时时间在具体调用的时候设置
		//RequestConfig defaultRequestConfig = getRequestConfig();
		
		httpClient = HttpClients
				.custom()
				.setRetryHandler(new DefaultHttpRequestRetryHandler(errorRetryCount,false))
				.setConnectionManager(cm)
				//.setDefaultRequestConfig(defaultRequestConfig)
				.setSSLSocketFactory(ssff)
				.build();
		return httpClient;
	}

	//"127.0.0.1", 8888, "https"
	public static void setProxy(String hostname,int port,String scheme)
	{
		proxy = new HttpHost(hostname,port,scheme);  		
	}
	public CloseableHttpClient initHttpClient() {
		// 也是池方式 默认的池
		HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
		httpClientBuilder.setDefaultCookieStore(cookieStore);

		// 重试次数 创建时就定好了
		httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(errorRetryCount, false));
		httpClientBuilder.setSSLSocketFactory(ssff);
		httpClient = httpClientBuilder.build();
		return httpClient;
	}

	private RequestConfig getRequestConfig() {
		RequestConfig defaultRequestConfig = RequestConfig.custom()
				.setSocketTimeout(connecttimeout*1000)
				.setConnectTimeout(connecttimeout * 1000)
				.setConnectionRequestTimeout(readtimeout * 1000)
				.setProxy(proxy)
				.build();
		return defaultRequestConfig;
	}

	public static void setSSLSocketFactory(SSLConnectionSocketFactory sf) throws Exception {
		ssff = sf;
	}

	public HttpClientAgent(String charset, int connecttimeout, int readtimeout) // throws
																				// Exception
	{

		this.connecttimeout = connecttimeout;
		this.readtimeout = readtimeout;
		this.charset = charset;// "GBK" "UTF-8"
		parmMap.clear();

		initHttpClient();
	}

	public void addRequestHeader(String headerName, String headerValue) {
		// postgetMethod.addRequestHeader(headerName, headerValue);
		headMap.put(headerName, headerValue);
	}

	public void addParm(String name, String value) {
		parmMap.put(name, value);
		// postMethod.addParameter(paramName, paramValue);
		return;
	}

	/*
	 * public HttpClientBaseAgent(String host, int port) throws Exception {
	 * client = new HttpClient(); setTimeout(30,60); parmlist.clear();
	 * 
	 * setHost(host,port); }
	 * 
	 * public void setHost(String host, int port) throws Exception {
	 * client.getHostConfiguration().setHost(host, port); this.host = host;
	 * this.port = port;
	 * 
	 * return; }
	 */

	public void sendPost(String url) throws BizException, UnknowStateException {
		postgetMethod = new HttpPost(url);
		postUrl(url);
	}

	// 出现任何异常 都要关闭连接
	// 连接超时 ，status不是OK，认为发送失败? 404是已经发送成功了，但是对方找不到返回的页面
	// 其它任何异常SocketTimeoutException，等，都认为发送成功
	// BizException异常，认为发送失败
	protected void postUrl(String url) throws BizConnectException, BizSysException,UnknowStateException {

		HttpPost postMethod = (HttpPost) postgetMethod;

		// 超时时间
		postMethod.setConfig(getRequestConfig());

		// 头信息

		Iterator it = headMap.entrySet().iterator();
		while (it.hasNext()) {
			Map.Entry entry = (Map.Entry) it.next();
			String key = (String) entry.getKey();
			String value = (String) entry.getValue();
			postMethod.setHeader(key, value);
		}

		// 参数
		ArrayList formParams = new ArrayList();
		it = parmMap.entrySet().iterator();
		while (it.hasNext()) {
			Map.Entry entry = (Map.Entry) it.next();
			String key = (String) entry.getKey();
			String value = (String) entry.getValue();
			formParams.add(new BasicNameValuePair(key, value));
		}
		// 字符集+参数
		try {
			UrlEncodedFormEntity urlEncodedFormEntity1 = new UrlEncodedFormEntity(formParams, charset);
			postMethod.setEntity(urlEncodedFormEntity1);
		} catch (Exception e) {
			throw new BizSysException(e, "添加参数失败" + e.getMessage());
		}
		// 是可以在执行前设置cookieStore的
		// httpContext.setCookieStore(cookieStore);

		try {
			httpResponse = httpClient.execute(postMethod, httpContext);
		}
		// 发送失败
		catch (ConnectTimeoutException e) {
			postMethod.releaseConnection();
			log.error(e.getMessage(), e);
			throw new BizConnectException(e, "连接超时" + e.getMessage());
		} catch (ConnectException e) {
			// 端口找不到 应该当失败
			// 连接被对方断开，也是返回这个。这时应该是超时
			// 要区分这两种才好
			// 只能根据描述区分了
			// Connection reset是被对方断开
			// Connection refused: connect 是拒绝连接

			postMethod.releaseConnection();
			if (e.getMessage().indexOf("refused") > 0)
				throw new BizConnectException("连接拒绝(Connection refused: connect)");
			else
				throw new UnknowStateException(e, e.getMessage());
		} catch (SocketTimeoutException e) {
			// 认为发送成功
			// 有异常就不再读取返回数据了
			postMethod.releaseConnection();
			throw new UnknowStateException(e, "读超时" + e.getMessage());
		} catch (SocketException e) {
			postMethod.releaseConnection();
			if (e.getMessage().indexOf("reset") > 0)
				throw new UnknowStateException(e, "连接被对方断开(Connection reset)");

		} catch (Exception e) {
			// UnknownHostException
			// 有异常就不再读取返回数据了
			postMethod.releaseConnection();
			throw new UnknowStateException(e, e.getMessage());
		}

		// 经常返回404 ,对方找不到返回的页面 或者对方action method没处理，
		// 这种情况，信息一般肯定发送过去了。只是对方没有处理方法导致
		int status = httpResponse.getStatusLine().getStatusCode();
		if (status != HttpStatus.SC_OK) {
			postMethod.releaseConnection();
			// postMethod.getURI()
			throw new UnknowStateException(url + "请求失败[" + status + "]");
		}

	}

	public void sendGet(String url) throws BizConnectException, UnknowStateException {

		postgetMethod = new HttpGet(url);
		HttpGet httpGet = (HttpGet) postgetMethod;
		// 重试次数

		// 超时时间
		httpGet.setConfig(getRequestConfig());

		Iterator it = headMap.entrySet().iterator();
		while (it.hasNext()) {
			Map.Entry entry = (Map.Entry) it.next();
			String key = (String) entry.getKey();
			String value = (String) entry.getValue();
			httpGet.setHeader(key, value);
		}

		try {
			httpResponse = httpClient.execute(postgetMethod);
			List<Cookie> cookies = cookieStore.getCookies();
		}
		// 发送失败
		catch (ConnectTimeoutException e) {
			releaseResponse();

			log.error(e.getMessage(), e);
			throw new BizConnectException(e, "连接超时" + e.getMessage());
		} catch (ConnectException e) {
			// 端口找不到 应该当失败
			// 连接被对方断开，也是返回这个。这时应该是超时
			// 要区分这两种才好
			// 只能根据描述区分了
			// Connection reset是被对方断开
			// Connection refused: connect 是拒绝连接
			releaseResponse();
			if (e.getMessage().indexOf("refused") > 0)
				throw new BizConnectException("连接拒绝(Connection refused: connect)");
			else
				throw new UnknowStateException(e, e.getMessage());
		} catch (SocketTimeoutException e) {
			// 认为发送成功
			// 有异常就不再读取返回数据了
			releaseResponse();
			throw new UnknowStateException(e, e.getMessage());
		} catch (SocketException e) {
			releaseResponse();
			if (e.getMessage().indexOf("reset") > 0)
				throw new UnknowStateException(e, "连接被对方断开(Connection reset)");

		} catch (Exception e) {
			// UnknownHostException
			// 有异常就不再读取返回数据了

			throw new UnknowStateException(e, e.getMessage());
		}
		int status = httpResponse.getStatusLine().getStatusCode();
		if (status != HttpStatus.SC_OK) {
			releaseResponse();
			throw new UnknowStateException(url + "请求失败[" + status + "]");// getMethod.getURI()
		}

	}

	public void setTimeout(int connectTimeout, int recvTimeout) {
		this.connecttimeout = connecttimeout;
		this.readtimeout = readtimeout;
	}

	// 在返回的状态码正确后，即可取得内容。取得目标地址的内容有三种方法：
	// 第一种，getResponseBody，该方法返回的是目标的二进制的byte流；
	public byte[] getResponseBody() throws UnknowStateException {
		try {

			String respStr = EntityUtils.toString(httpResponse.getEntity(), charset);
			releaseResponse();
			return respStr.getBytes(charset);
		} catch (Throwable t) {
			throw new UnknowStateException(t, t.getMessage());
		}
	}

	// 第二种，getResponseBodyAsString，这个方法返回的是String类型，值得注意的是该方法返回的String的编码是根据系统默认的编码方式，所以返回的String值可能编码类型有误，在本文的"字符编码"部分中将对此做详细介绍；
	public String getResponseBodyAsString() throws UnknowStateException {
		try {
			String respStr = EntityUtils.toString(httpResponse.getEntity(), charset);
			releaseResponse();
			return respStr;

		} catch (Throwable t) {
			throw new UnknowStateException(t, t.getMessage());
		}

	}

	// 第三种，getResponseJavaObject，这个方法对于目标地址中有大量数据需要传输是最佳的。但必须是java Object
	public Serializable getResponseJavaObject() throws UnknowStateException {
		java.io.ObjectInputStream oInput = null;
		Serializable returnObj = null;
		try {

			InputStream in = httpResponse.getEntity().getContent();
			oInput = new java.io.ObjectInputStream(in);
			returnObj = (Serializable) oInput.readObject();
			oInput.close();
			oInput = null;
		} catch (Throwable t) {
			log.error(t.getMessage(), t);
			throw new UnknowStateException(t, t.getMessage());
		} finally {
			releaseResponse();// 不知道 关闭了后，流是否也关闭
			try {
				if (oInput != null) {
					oInput.close();
					oInput = null;

				}
			} catch (Throwable t) {
				throw new UnknowStateException(t, t.getMessage());
			}

		}
		return returnObj;
	}

	// 第4种，getResponseBodyStream 必须主动关闭连接
	public InputStream getResponseBodyStream() throws Exception {
		InputStream in = httpResponse.getEntity().getContent();
		return in;
	}

	// 释放连接。无论执行方法是否成功，都必须释放连接。 method.releaseConnection();
	// httpclient4采用连接池的方式，是没必要关闭连接的
	public void releaseConnection() {
		// postgetMethod.releaseConnection();
		try {
			httpClient.close();
		} catch (Exception e) {
			log.info("关闭连接出错" + e.getMessage());
		}
	}

	public void releaseResponse() {
		try {
			httpResponse.close();
		} catch (Exception e) {
			log.info("关闭Response出错" + e.getMessage());
		}
	}
	public void releaseResponse2(){
		try {
			EntityUtils.consume(httpResponse.getEntity());
		} catch (Exception e) {
			log.info("关闭Response出错" + e.getMessage());
		}
	}
	public void postObject(String url, Serializable inputObj) throws BizConnectException,BizSysException, UnknowStateException {

		// postMethod.setRequestHeader("Content-Type",
		// "application/octet-stream");
		// postMethod.setRequestHeader("content-type",
		// "application/x-www-form-urlencoded");
		// postMethod.setRequestHeader("content-type", "multipart/form-data");

		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		ObjectOutputStream oos = null;
		try {
			oos = new ObjectOutputStream(baos);
			oos.writeObject(inputObj);
			oos.flush();
			oos.close();
			oos = null;
		} catch (IOException e) {
			throw new BizSysException(e, e.getMessage());
		}
		byte[] tmpArray = baos.toByteArray();
		ByteArrayInputStream bis = new ByteArrayInputStream(tmpArray);
		HttpPost postMethod = (HttpPost) postgetMethod;
		postMethod.setEntity(new ByteArrayEntity(tmpArray));
		postUrl(url);

	}

	public void postBytes(String url, byte[] bytes, String contentType) throws BizConnectException,BizSysException,
			UnknowStateException {
		HttpPost postMethod = (HttpPost) postgetMethod;
		postMethod.setEntity(new ByteArrayEntity(bytes));
		if (contentType != null)
			postMethod.setHeader("Content-type", contentType);
		postUrl(url);
	}

	public void postString(String url, String inputStr, String contentType) throws BizConnectException,BizSysException,
			UnknowStateException {
		postBytes(url, inputStr.getBytes(), contentType);
	}

	// 关闭掉连接 仍然可以去到cookies
	public List getCookies() {
		return cookieStore.getCookies();
	}
	public Header[] getHeaders() {
		return httpResponse.getAllHeaders();
	}
	public static void setErrorRetryCount(int count) {
		errorRetryCount = count;
	}

	public String getCharset() {
		return charset;
	}

	public void setCharset(String charset) {
		this.charset = charset;
	}

}
